﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace VincentUtil.Ntp
{
    internal static class NtpUtil
    {

        /// <summary>
        /// 尝试将时间写入NTP协议报文
        /// </summary>
        /// <param name="ntpData">ntp报文</param>
        /// <param name="startIndex">起始索引，长度为8</param>
        /// <param name="time">UTC时间</param>
        /// <returns>是否写入成功</returns>
        internal static bool TryWriteNtpTime(byte[] ntpData, int startIndex, DateTime time)
        {
            DateTime ntpEpoch = new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc);
            TimeSpan elapsedTime = time - ntpEpoch;

            ulong seconds = (ulong)elapsedTime.TotalSeconds;
            ulong fraction = (ulong)((elapsedTime.TotalSeconds - seconds) * 0x100000000L);
            ulong timestamp = (seconds << 32) | fraction;
            byte[] timestampBytes = BitConverter.GetBytes(timestamp);

            // NTP时间戳在网络中是大端序，BitConverter.GetBytes返回的是小端序
            // 因此需要将字节顺序反转
            if (BitConverter.IsLittleEndian)
            {
                Array.Reverse(timestampBytes);
            }

            // 设置Reference Timestamp
            Array.Copy(timestampBytes, 0, ntpData, startIndex, 8);
            return true;
        }

        /// <summary>
        /// 解析NTP报文中为时间字段
        /// </summary>
        /// <param name="ntpData">ntp报文</param>
        /// <param name="startIndex">起始索引，长度为8</param>
        /// <param name="time">UTC时间</param>
        /// <returns>是否解析成功</returns>
        internal static bool TryResolveNtpTime(byte[] ntpData, int startIndex, out DateTime time)
        {
            time = DateTime.UtcNow;

            if (ntpData.Length <= startIndex + 4) return false;

            //Get the seconds part
            ulong intPart = BitConverter.ToUInt32(ntpData, startIndex);

            //Get the seconds fraction
            ulong fractPart = BitConverter.ToUInt32(ntpData, startIndex + 4);

            //Convert From big-endian to little-endian
            intPart = SwapEndianness(intPart);
            fractPart = SwapEndianness(fractPart);

            var milliseconds = (intPart * 1000) + ((fractPart * 1000) / 0x100000000L);

            //**UTC** time
            time = (new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc)).AddMilliseconds((long)milliseconds);
            return true;
        }

        private static uint SwapEndianness(ulong x)
        {
            return (uint)(((x & 0x000000ff) << 24) +
            ((x & 0x0000ff00) << 8) +
            ((x & 0x00ff0000) >> 8) +
            ((x & 0xff000000) >> 24));
        }

    }
}
